﻿/*              Copyright(c) 2008 Dave Sexton              *
 * http://www.codeplex.com/DocProject                      *
 * http://www.codeplex.com/DocProject/Project/License.aspx *
 ***********************************************************/
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;

namespace DaveSexton.DocToMaml
{
	internal sealed class Project : ProjectBase
	{
		#region Public Properties
		public string LastOutputDirectory
		{
			get
			{
				return lastOutputDirectory;
			}
			set
			{
				if (!string.Equals(lastOutputDirectory, value, StringComparison.OrdinalIgnoreCase))
				{
					lastOutputDirectory = value;
					OnStateChanged(EventArgs.Empty);
				}
			}
		}

		public InputCollection Inputs
		{
			get
			{
				return inputs;
			}
		}

		public FileGuidManager Guids
		{
			get
			{
				return guids;
			}
		}

		public string ConversionLog
		{
			get
			{
				if (inputs.Count == 0)
					return Resources.Text.ProjectWithoutInputs;
	
				StringBuilder log = new StringBuilder(1024);

				foreach (IInput input in inputs)
				{
					log.Append(input.Log);
					log.AppendLine();
					log.AppendLine();
				}

				return log.ToString();
			}
		}
		#endregion

		#region Private / Protected
		private string lastOutputDirectory;
		private readonly InputCollection inputs;
		private readonly RuleContainer rules;
		private readonly ConceptualImageManager images;
		private readonly FileGuidManager guids;
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="Project" /> class.
		/// </summary>
		public Project(string file, InputCollection inputs, RuleContainer rules, ConceptualImageManager images, FileGuidManager guids)
			: base(file)
		{
			if (inputs == null)
				throw new ArgumentNullException("inputs");

			if (rules == null)
				throw new ArgumentNullException("rules");

			if (images == null)
				throw new ArgumentNullException("images");

			if (guids == null)
				throw new ArgumentNullException("guids");

			this.inputs = inputs;
			this.rules = rules;
			this.images = images;
			this.guids = guids;
		}
		#endregion

		#region Methods
		[System.Diagnostics.DebuggerHidden]
		private static void ThrowUnexpectedElementError(XmlReader reader)
		{
			IXmlLineInfo line = (IXmlLineInfo) reader;

			throw new XmlException(string.Format(System.Globalization.CultureInfo.CurrentCulture,
				Resources.Errors.InvalidProjectFileUnexpectedElement, reader.Name), null, line.LineNumber, line.LinePosition);
		}

		[System.Diagnostics.DebuggerHidden]
		private static void ThrowInvalidInputUriError(XmlReader reader, string value)
		{
			IXmlLineInfo line = (IXmlLineInfo) reader;

			throw new XmlException(string.Format(System.Globalization.CultureInfo.CurrentCulture,
				Resources.Errors.InvalidProjectFileInputUri, value), null, line.LineNumber, line.LinePosition);
		}

		public FileInput CreateFileInput(string file)
		{
			FileInput input = new FileInput(file, images, guids);
			input.Rules.DefaultRules = rules;
			return input;
		}

		public FolderInput CreateFolderInput(string path)
		{
			FolderInput input = new FolderInput(path, images, guids);
			input.Rules.DefaultRules = rules;
			return input;
		}

		#region Read Project File
		protected override void ReadProjectFile(System.Xml.XmlReader reader)
		{
			// read the project node and check that the version attribute is valid
			ReadProjectStart(reader);

			reader.Read();	// read project node

			do
			{
				if (reader.NodeType == XmlNodeType.Element && reader.Depth == 1)
				{
					switch (reader.Name)
					{
						case "rules":
							using (XmlReader rulesReader = reader.ReadSubtree())
							{
								rulesReader.MoveToContent();

								ReadInputRules(rulesReader, rules);
							}
							reader.Read();
							break;
						case "inputs":
							using (XmlReader inputsReader = reader.ReadSubtree())
							{
								inputsReader.MoveToContent();

								ReadInputs(inputsReader);
							}
							reader.Read();
							break;
						case "output":
							if (LastOutputDirectory != null)
								ThrowUnexpectedElementError(reader);

							lastOutputDirectory = reader.ReadElementString();
							break;
						default:
							ThrowUnexpectedElementError(reader);
							break;
					}
				}
				else
					reader.Read();
			}
			while (!reader.EOF);
		}

		private void ReadInputs(XmlReader reader)
		{
			while (reader.Read())
			{
				if (reader.NodeType == XmlNodeType.Element && reader.Depth == 1)
				{
					string name = reader.Name;
					string path = reader.GetAttribute("path");

					Uri uri;
					if (!Uri.TryCreate(path, UriKind.RelativeOrAbsolute, out uri))
						ThrowInvalidInputUriError(reader, path);

					if (uri.IsAbsoluteUri)
						path = uri.LocalPath;
					else
						path = System.IO.Path.GetFullPath(
							System.IO.Path.Combine(System.IO.Path.GetDirectoryName(FilePath), path));

					if (!inputs.Contains(path))
					{
						IInput input = null;

						if (string.Equals(name, "file", StringComparison.Ordinal))
							input = CreateFileInput(path);
						else if (string.Equals(name, "folder", StringComparison.Ordinal))
							input = CreateFolderInput(path);
						else
							ThrowUnexpectedElementError(reader);

						if (!reader.IsEmptyElement)
						{
							using (XmlReader inputReader = reader.ReadSubtree())
							{
								inputReader.MoveToContent();

								ReadInputRules(inputReader, input.Rules);
							}
						}

						inputs.Add(input);
					}
				}
			}
		}

		private static void ReadInputRules(XmlReader reader, IRuleContainer rules)
		{
			while (reader.Read())
			{
				if (reader.NodeType == XmlNodeType.Element && reader.Depth == 1)
				{
					switch (reader.Name)
					{
						case "remove":
							using (XmlReader subtree = reader.ReadSubtree())
							{
								subtree.MoveToContent();
								reader.Read();

								ReadRemovableInputRules(subtree, rules);
							}
							break;
						case "replace":
							using (XmlReader subtree = reader.ReadSubtree())
							{
								subtree.MoveToContent();
								reader.Read();

								ReadReplaceableInputRules(subtree, rules);
							}
							break;
						case "convert":
							using (XmlReader subtree = reader.ReadSubtree())
							{
								subtree.MoveToContent();
								reader.Read();

								ReadMamlConversionInputRules(subtree, rules);
							}
							break;
						default:
							ThrowUnexpectedElementError(reader);
							break;
					}
				}
			}
		}

		private static void ReadRemovableInputRules(XmlReader reader, IRuleContainer rules)
		{
			do
			{
				if (reader.NodeType == XmlNodeType.Element && reader.Depth == 1)
				{
					switch (reader.Name)
					{
						case "attribute":
							rules.Add(new RemoveRule(reader.ReadElementContentAsString(), RuleMatchKind.Attribute));
							break;
						case "tag":
							rules.Add(new RemoveRule(reader.ReadElementContentAsString(), RuleMatchKind.TagName));
							break;
						case "id":
							rules.Add(new RemoveRule(reader.ReadElementContentAsString(), RuleMatchKind.ElementId));
							break;
						case "class":
							rules.Add(new RemoveRule(reader.ReadElementContentAsString(), RuleMatchKind.StyleSheetClass));
							break;
						default:
							ThrowUnexpectedElementError(reader);
							break;
					}
				}
				else
					reader.Read();
			}
			while (!reader.EOF);
		}

		private static void ReadReplaceableInputRules(XmlReader reader, IRuleContainer rules)
		{
			do
			{
				if (reader.NodeType == XmlNodeType.Element && reader.Depth == 1)
				{
					switch (reader.Name)
					{
						case "attribute":
							reader.Read();
							rules.Add(new ReplaceRule(reader.ReadElementString("match"), RuleMatchKind.Attribute, reader.ReadElementString("value")));
							break;
						case "tag":
							reader.Read();
							rules.Add(new ReplaceRule(reader.ReadElementString("match"), RuleMatchKind.TagName, reader.ReadElementString("value")));
							break;
						default:
							ThrowUnexpectedElementError(reader);
							break;
					}
				}
				else
					reader.Read();
			}
			while (!reader.EOF);
		}

		private static void ReadMamlConversionInputRules(XmlReader reader, IRuleContainer rules)
		{
			do
			{
				if (reader.NodeType == XmlNodeType.Element && reader.Depth == 1)
				{
					switch (reader.Name)
					{
						case "tag":
							reader.Read();
							rules.Add(new ConvertRule(reader.ReadElementString("match"), RuleMatchKind.TagName,
								(Maml.MamlElementType) Enum.Parse(typeof(Maml.MamlElementType), reader.ReadElementString("value"), true)));
							break;
						case "id":
							reader.Read();
							rules.Add(new ConvertRule(reader.ReadElementString("match"), RuleMatchKind.ElementId,
								(Maml.MamlElementType) Enum.Parse(typeof(Maml.MamlElementType), reader.ReadElementString("value"), true)));
							break;
						case "class":
							reader.Read();
							rules.Add(new ConvertRule(reader.ReadElementString("match"), RuleMatchKind.StyleSheetClass,
								(Maml.MamlElementType) Enum.Parse(typeof(Maml.MamlElementType), reader.ReadElementString("value"), true)));
							break;
						default:
							ThrowUnexpectedElementError(reader);
							break;
					}
				}
				else
					reader.Read();
			}
			while (!reader.EOF);
		}
		#endregion

		#region Write Project File
		protected override void WriteProjectFile(System.Xml.XmlWriter writer)
		{
			base.WriteProjectStart(writer);

			writer.WriteStartElement("rules");
			WriteInputRules(writer, rules);
			writer.WriteEndElement();

			writer.WriteStartElement("inputs");
			WriteInputs(writer);
			writer.WriteEndElement();

			if (!string.IsNullOrEmpty(LastOutputDirectory))
				writer.WriteElementString("output", LastOutputDirectory);

			base.WriteProjectEnd(writer);
		}

		private void WriteInputs(XmlWriter writer)
		{
			foreach (IInput input in inputs)
			{
				if (input is FolderInput)
					writer.WriteStartElement("folder");
				else
					writer.WriteStartElement("file");

				writer.WriteAttributeString("path", MakeRelativePath(input.FullPath));

				WriteInputRules(writer, input.Rules);

				writer.WriteEndElement();
			}
		}

		private static void WriteInputRules(XmlWriter writer, IRuleContainer rules)
		{
			WriteRemovableInputRules(writer, rules.GetRules(RuleAction.Remove));

			WriteReplaceableInputRules(writer, rules.GetRules(RuleAction.Replace));

			WriteMamlConversionInputRules(writer, rules.GetRules(RuleAction.Convert));
		}

		private static void WriteRemovableInputRules(XmlWriter writer, RuleCollection rules)
		{
			if (rules.Count > 0)
			{
				writer.WriteStartElement("remove");

				foreach (IRule rule in rules)
				{
					switch (rule.MatchKind)
					{
						case RuleMatchKind.Attribute:
							writer.WriteElementString("attribute", rule.Match);
							break;
						case RuleMatchKind.TagName:
							writer.WriteElementString("tag", rule.Match);
							break;
						case RuleMatchKind.ElementId:
							writer.WriteElementString("id", rule.Match);
							break;
						case RuleMatchKind.StyleSheetClass:
							writer.WriteElementString("class", rule.Match);
							break;
					}
				}

				writer.WriteEndElement();
			}
		}

		private static void WriteReplaceableInputRules(XmlWriter writer, RuleCollection rules)
		{
			if (rules.Count > 0)
			{
				writer.WriteStartElement("replace");

				foreach (IRule rule in rules)
				{
					switch (rule.MatchKind)
					{
						case RuleMatchKind.Attribute:
							writer.WriteStartElement("attribute");
							break;
						case RuleMatchKind.TagName:
							writer.WriteStartElement("tag");
							break;
						default:
							throw new InvalidOperationException(Resources.Errors.RulesErrorMatchKindInvalid);
					}

					writer.WriteElementString("match", rule.Match);
					writer.WriteElementString("value", (string) rule.Value);
					writer.WriteEndElement();
				}

				writer.WriteEndElement();
			}
		}

		private static void WriteMamlConversionInputRules(XmlWriter writer, RuleCollection rules)
		{
			if (rules.Count > 0)
			{
				writer.WriteStartElement("convert");

				foreach (IRule rule in rules)
				{
					switch (rule.MatchKind)
					{
						case RuleMatchKind.TagName:
							writer.WriteStartElement("tag");
							break;
						case RuleMatchKind.ElementId:
							writer.WriteStartElement("id");
							break;
						case RuleMatchKind.StyleSheetClass:
							writer.WriteStartElement("class");
							break;
						default:
							throw new InvalidOperationException(Resources.Errors.RulesErrorMatchKindInvalid);
					}

					writer.WriteElementString("match", rule.Match);
					writer.WriteElementString("value", rule.Value.ToString());
					writer.WriteEndElement();
				}

				writer.WriteEndElement();
			}
		}
		#endregion
		#endregion
	}
}
